/*
 * Chiper.cpp
 *
 *  Created on: Feb 28, 2019
 *      Author: joseph
 */

#include "Cipher1.h"
 
#define MBEDTLS_DES_CRYPT_ECB_ALT
#define MBEDTLS_PKCS5_C
Cipher1::Cipher1()
{
  // default unsecure key, its highly recommended to use the overloaded constructor and the function setKey()
  // sometimes serval keys wont work
  // https://tls.mbed.org/kb/how-to/generate-an-aes-key

  setKey("abcdefghijklmnop");
}

Cipher1::Cipher1(char *key)
{
  setKey(key);
}

Cipher1::~Cipher1()
{
  delete privateCipherKey;
}

void Cipher1::setKey(char *key)
{
  // aes-128bit mode means that your cipher key can only be 16 characters long
  // futhermore, only chracters in the cipher key are allowed, not numbers!
  // 16 characters + '\0'

  if (strlen(key) > 16)
  {
    privateCipherKey = new char[17];
    (String(key).substring(0, 16)).toCharArray(privateCipherKey, 17);

#ifdef CIPHER_DEBUG
    Serial.println("[cipher] error: cipher key to long! Will be cutted to 16 characters.");
    Serial.println("[cipher] => " + String(key));
    Serial.println("[cipher] => " + String(privateCipherKey));
#endif
  }
  else if (strlen(key) < 16)
  {
    privateCipherKey = "abcdefghijklmnop";

#ifdef CIPHER_DEBUG
    Serial.println("[cipher] error: cipher key to short! Standard cipher key will be used.");
#endif
  }
  else
  {
#ifdef CIPHER_DEBUG
    Serial.println("[cipher] cipher key length matched. Using this key.");
#endif
    privateCipherKey = key;
  }
}

char *Cipher1::getKey()
{
  return privateCipherKey;
}

void Cipher1::encrypt(char *plainText, char *key, unsigned char *outputBuffer)
{
  // encrypt plainText buffer of length 16 characters
  mbedtls_aes_context aes;

  mbedtls_aes_init(&aes);

  mbedtls_aes_setkey_enc(&aes, (const unsigned char *)key, strlen(key) * 8);
  mbedtls_aes_crypt_ecb(&aes, MBEDTLS_AES_ENCRYPT, (const unsigned char *)plainText, outputBuffer);

  mbedtls_aes_free(&aes);
}

void Cipher1::encrypt(char *plainText, unsigned char *outputBuffer)
{
  encrypt(plainText, getKey(), outputBuffer);
}

void Cipher1::decrypt(unsigned char *cipherText, char *key, unsigned char *outputBuffer)
{
  // encrypt ciphered chipherText buffer of length 16 characters to plain text
  mbedtls_aes_context aes;

  mbedtls_aes_init(&aes);
  mbedtls_aes_setkey_dec(&aes, (const unsigned char *)key, strlen(key) * 8);
  mbedtls_aes_crypt_ecb(&aes, MBEDTLS_AES_DECRYPT, (const unsigned char *)cipherText, outputBuffer);
  mbedtls_aes_free(&aes);
}

void Cipher1::decrypt(unsigned char *cipherText, unsigned char *outputBuffer)
{
  decrypt(cipherText, getKey(), outputBuffer);
}

String Cipher1::encryptBuffer(char *plainText, char *key)
{
  // returns encrypted String of plainText (length: 16 characters)
  String cipherTextString = "";
  unsigned char cipherTextOutput[16];

  encrypt(plainText, key, cipherTextOutput);

  for (int i = 0; i < 16; i++)
  {
    cipherTextString = cipherTextString + (char)cipherTextOutput[i];
  }

  return cipherTextString;
}

String Cipher1::encryptBuffer(char *plainText)
{
  return encryptBuffer(plainText, getKey());
}

String Cipher1::decryptBuffer(String cipherText, char *key)
{
  // returns decrypted String of ciphered text (length: 16 characters)
  String decipheredTextString = "";
  unsigned char cipherTextOutput[16];
  unsigned char decipheredTextOutput[16];

  for (int i = 0; i < 16; i++)
  {
    cipherTextOutput[i] = (char)cipherText[i];
  }

  decrypt(cipherTextOutput, key, decipheredTextOutput);

  for (int i = 0; i < 16; i++)
  {
    decipheredTextString = decipheredTextString + (char)decipheredTextOutput[i];

    if (decipheredTextString[i] == '\0')
    {
      break;
    }
  }

  return decipheredTextString;
}

String Cipher1::decryptBuffer(String cipherText)
{
  return decryptBuffer(cipherText, getKey());
}

String Cipher1::encryptString(String plainText, char *key)
{
  // returns encrypted String of plainText with variable length
  constexpr int BUFF_SIZE = 16;
  String buffer = "";
  String cipherTextString = "";
  int index = plainText.length() / BUFF_SIZE;

  for (int block = 0; block < plainText.length() / BUFF_SIZE; block++)
  {
    for (int j = block * BUFF_SIZE; j < (block + 1) * BUFF_SIZE; j++)
    {
      buffer += plainText[j];
    }

    cipherTextString += encryptBuffer(const_cast<char *>(buffer.c_str()), key);
    buffer = "";
  }

  buffer = "";

  if (plainText.length() % BUFF_SIZE > 0)
  {
    for (int bytes_read = (index * BUFF_SIZE); bytes_read <= (index * BUFF_SIZE) + plainText.length() % BUFF_SIZE; bytes_read++)
    {
      buffer += plainText[bytes_read];
    };
    cipherTextString += encryptBuffer(const_cast<char *>(buffer.c_str()), key);
  }

  return cipherTextString;
}

String Cipher1::encryptString(String plainText)
{
  return encryptString(plainText, getKey());
}

String Cipher1::decryptString(String cipherText, char *key)
{
  // returns encrypted String of plainText with variable length
  constexpr int BUFF_SIZE = 16;
  String buffer = "";
  String decipheredTextString = "";

  for (int block = 0; block < cipherText.length() / BUFF_SIZE; block++)
  {
    for (int j = block * BUFF_SIZE; j < (block + 1) * BUFF_SIZE; j++)
    {
      buffer += cipherText[j];
    }

    decipheredTextString += decryptBuffer(buffer, key);
    buffer = "";
  }

  return decipheredTextString;
}

String Cipher1::decryptString(String cipherText)
{
  return decryptString(cipherText, getKey());
}
